#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <errno.h>

#include <vector>
#include <string>

#include <db.h>

#include <utils.h>
#include <lua_wrap.h>

#define PAGE_SIZE       8192

// table path
#define DBFN_PATH       "path.db"
#define DBFN_PATH_FN    "path-fn.db"

// table def
#define DBFN_DEF        "def.db"
#define DBFN_DEF_FID    "def-fid.db"
#define DBFN_DEF_PID    "def-pid.db"
#define DBFN_DEF_NAME   "def-name.db"

// table ref
#define DBFN_REF        "ref.db"
#define DBFN_REF_FID    "ref-fid.db"
#define DBFN_REF_NAME   "ref-name.db"

#define FN_SEQ          "seq"

#define CREATE_MODE     0
#define UPDATE_MODE     1
#define QUERY_MODE      2

static Process         *processes;
static LuaArgs          pred_args;
static LuaArgs          pred_rets;

static int              g_mode = CREATE_MODE;

static std::string      g_db_root;

static int              g_nprocesses = 2;

static int              g_show_inherit = 0;

StringVector g_ignored_dir;

#define CNT_GET_INT(f)                          \
    do {                                        \
        f = 0;                                  \
        if (*ct == '-') {                       \
            ++ct;                               \
            sign = -1;                          \
        }                                       \
        else                                    \
            sign = 1;                           \
        for (;;) {                              \
            c = *ct++;                          \
            if (c >= '0' && c <= '9') {         \
                f *= 10;                        \
                f += c - '0';                   \
            }                                   \
            else                                \
                break;                          \
        }                                       \
        f *= sign;                              \
    } while (0)

#define CNT_GET_STR(f)                          \
    do {                                        \
        f.clear();                              \
        for (;;) {                              \
            c = *ct++;                          \
            if (c == '|' || c == 0)             \
                break;                          \
            else                                \
                f.push_back(c);                 \
        }                                       \
    } while (0)

// fid|pid|pos|name|type
inline static int
parse_def_content(const char   *ct,
                  int&          fid,
                  int&          pid,
                  int&          pos,
                  std::string&  name,
                  std::string&  type)
{
    char c;
    int  sign;

    CNT_GET_INT(fid);
    if (c != '|')
        return -1;
    CNT_GET_INT(pid);
    if (c != '|')
        return -1;
    CNT_GET_INT(pos);
    if (c != '|')
        return -1;
    CNT_GET_STR(name);
    if (c != '|')
        return -1;
    CNT_GET_STR(type);

    return (c == 0) ? 0 : -1;
}

// fid|pos|name
inline static int
parse_ref_content(const char   *ct,
                  int&          fid,
                  int&          pos,
                  std::string&  name)
{
    char c;
    int  sign;

    CNT_GET_INT(fid);
    if (c != '|')
        return -1;
    CNT_GET_INT(pos);
    if (c != '|')
        return -1;
    CNT_GET_STR(name);

    return (c == 0) ? 0 : -1;
}

struct Database {
    DB         *path;

    DB         *path_fn;

    DB         *def;

    DB         *def_fid;

    DB         *def_pid;

    DB         *def_name;

    DB         *ref;

    DB         *ref_fid;

    DB         *ref_name;

    // cursors
    DBC        *path_cursor;

    DBC        *path_fn_cursor;

    DBC        *def_cursor;

    DBC        *def_fid_cursor;

    DBC        *def_pid_cursor;

    DBC        *def_name_cursor;

    DBC        *ref_cursor;

    DBC        *ref_fid_cursor;

    DBC        *ref_name_cursor;

    Database() : path(NULL), path_fn(NULL)
               , def(NULL), def_fid(NULL)
               , def_pid(NULL), def_name(NULL)
               , ref(NULL), ref_fid(NULL)
               , ref_name(NULL)
    {
    }

    bool all_opened_p() const {
        return (path && path_fn && def
                && def_fid && def_pid && def_name
                && ref && ref_fid && ref_name);
    }

    void create_cursors() {
        path->cursor(path, NULL, &path_cursor, 0);
        path_fn->cursor(path_fn, NULL, &path_fn_cursor, 0);

        def->cursor(def, NULL, &def_cursor, 0);
        def_fid->cursor(def_fid, NULL, &def_fid_cursor, 0);
        def_pid->cursor(def_pid, NULL, &def_pid_cursor, 0);
        def_name->cursor(def_name, NULL, &def_name_cursor, 0);

        ref->cursor(ref, NULL, &ref_cursor, 0);
        ref_fid->cursor(ref_fid, NULL, &ref_fid_cursor, 0);
        ref_name->cursor(ref_name, NULL, &ref_name_cursor, 0);
    }

    void close_cursors() {
        path_cursor->close(path_cursor);
        path_fn_cursor->close(path_fn_cursor);

        def_cursor->close(def_cursor);
        def_pid_cursor->close(def_pid_cursor);
        def_fid_cursor->close(def_fid_cursor);
        def_name_cursor->close(def_name_cursor);

        ref_cursor->close(ref_cursor);
        ref_fid_cursor->close(ref_fid_cursor);
        ref_name_cursor->close(ref_name_cursor);
    }

    inline void del_something_map_to_id(DBC *cursor,
                                        DBT *pkey,
                                        int  id)
    {
        DBT val;

        memset(&val, 0, sizeof(DBT));

        if (cursor->get(cursor, pkey, &val, DB_SET) == 0) {
            do {
                if (*(int *)val.data == id) {
                    cursor->del(cursor, 0);
                }
            }
            while (cursor->get(cursor, pkey, &val, DB_NEXT_DUP) == 0);
        }
    }

    // pid => id in def table
    // only pid >= 0 in the table.
    void del_def_pid(int id, int pid) {
        if (pid >= 0) {
            DBT key;

            memset(&key, 0, sizeof(DBT));

            key.data = (void *) &pid;
            key.size = sizeof(int);

            del_something_map_to_id(def_pid_cursor, &key, id);
        }
    }

    // name => id in def table.
    void del_def_name(int id, const char *name, int name_len) {
        DBT key;

        memset(&key, 0, sizeof(DBT));

        key.data = (void *)name;
        key.size = name_len + 1;

        del_something_map_to_id(def_name_cursor, &key, id);
    }

    void del_ref_name(int id, const char *name, int name_len) {
        DBT key;

        memset(&key, 0, sizeof(DBT));

        key.data = (void *)name;
        key.size = name_len + 1;

        del_something_map_to_id(ref_name_cursor, &key, id);
    }

    int get_file_id_and_del(const char *fn, int fn_len)
    {
        DBT key, val;

        memset(&key, 0, sizeof(DBT));
        memset(&val, 0, sizeof(DBT));

        key.data = (void *)fn;
        key.size = fn_len + 1;

        if (path_fn_cursor->get(path_fn_cursor, &key, &val, DB_SET) == 0) {
            int id = *(int *) val.data;
            path_fn_cursor->del(path_fn_cursor, 0);
            return id;
        }

        return -1;
    }

    int path_to_id(const char *fn, int fn_len)
    {
        DBT key, val;

        memset(&key, 0, sizeof(DBT));
        memset(&val, 0, sizeof(DBT));

        key.data = (void *)fn;
        key.size = fn_len + 1;

        if (path_fn_cursor->get(path_fn_cursor, &key, &val, DB_SET) == 0)
            return *(int *) val.data;

        return -1;
    }

    const char *id_to_path(int id)
    {
        DBT key, val;

        memset(&key, 0, sizeof(DBT));
        memset(&val, 0, sizeof(DBT));

        key.data = (void *)&id;
        key.size = sizeof(int);

        if (path_cursor->get(path_cursor, &key, &val, DB_SET) == 0)
            return (const char *) val.data;

        return NULL;
    }

    void print_def_by_id(int id)
    {
        DBT key, val;

        memset(&key, 0, sizeof(DBT));
        memset(&val, 0, sizeof(DBT));

        key.data = (void *)&id;
        key.size = sizeof(int);

        if (def_cursor->get(def_cursor, &key, &val, DB_SET) == 0) {
            int         fid, pid, pos;
            std::string name, type;

            if (parse_def_content((const char *)val.data,
                                  fid, pid, pos, name, type) == 0) {
                // lineno       name    filename
                const char *fn = id_to_path(fid);

                if (fn == NULL)
                    message(MSG_LEVEL_ERROR, "can't get filename, id = %d\n", fid);
                else {
                    if (g_show_inherit)
                        printf("%d\t%d\t%d\t%s\t%s\t%s\n",
                               id, pid, pos, name.c_str(),
                               type.c_str(), fn);
                    else
                        printf("%d\t%s\t%s\t%s\n",
                               pos, name.c_str(),
                               type.c_str(), fn);
                }
            }
            else
                message(MSG_LEVEL_ERROR, "can't parse def content: %s\n",
                        (const char *)val.data);
        }
        else
            message(MSG_LEVEL_ERROR, "can't get def, id = %d\n", id);
    }

    void print_def_by_id_with_filename(int id, const char *fn)
    {
        DBT key, val;

        memset(&key, 0, sizeof(DBT));
        memset(&val, 0, sizeof(DBT));

        key.data = (void *)&id;
        key.size = sizeof(int);

        if (def_cursor->get(def_cursor, &key, &val, DB_SET) == 0) {
            int         fid, pid, pos;
            std::string name, type;

            if (parse_def_content((const char *)val.data,
                                  fid, pid, pos, name, type) == 0) {
                // lineno       name    filename
                // only print the top level of the file
                if (g_show_inherit)
                    printf("%d\t%d\t%d\t%s\t%s\t%s\n",
                           id, pid, pos, name.c_str(),
                           type.c_str(), fn);
                else
                    printf("%d\t%s\t%s\t%s\n",
                           pos, name.c_str(),
                           type.c_str(), fn);
            }
            else
                message(MSG_LEVEL_ERROR, "can't parse def content: %s\n",
                        (const char *)val.data);
        }
        else
            message(MSG_LEVEL_ERROR, "can't get def, id = %d\n", id);
    }

    void print_def_by_name(const char *name, int name_len)
    {
        // find ids from def_name table
        DBT key, val;

        memset(&key, 0, sizeof(DBT));
        memset(&val, 0, sizeof(DBT));

        key.data = (void *)name;
        key.size = name_len + 1;

        if (def_name_cursor->get(def_name_cursor, &key, &val, DB_SET) == 0) {
            do {
                int id = *(int *)val.data;

                print_def_by_id(id);
            }
            while (def_name_cursor->get(def_name_cursor, &key, &val, DB_NEXT_DUP) == 0);
        }
    }

    void print_def_by_filename(const char *fn, int fn_len)
    {
        int fid = path_to_id(fn, fn_len);

        if (fid < 0) {
            message(MSG_LEVEL_ERROR, "can't found file `%s'\n", fn);
            return;
        }

        // find the def id from def_fid table
        DBT key, val;

        memset(&key, 0, sizeof(DBT));
        memset(&val, 0, sizeof(DBT));

        key.data = (void *)&fid;
        key.size = sizeof(int);

        if (def_fid_cursor->get(def_fid_cursor, &key, &val, DB_SET) == 0) {
            do {
                int id = *(int *)val.data;

                print_def_by_id_with_filename(id, fn);
            }
            while (def_fid_cursor->get(def_fid_cursor, &key, &val, DB_NEXT_DUP) == 0);
        }
    }

    void print_ref_by_id(int id)
    {
        DBT key, val;

        memset(&key, 0, sizeof(DBT));
        memset(&val, 0, sizeof(DBT));

        key.data = (void *)&id;
        key.size = sizeof(int);

        if (ref_cursor->get(ref_cursor, &key, &val, DB_SET) == 0) {
            int         fid, pos;
            std::string name;

            if (parse_ref_content((const char *)val.data,
                                  fid, pos, name) == 0) {
                // lineno       name    filename
                const char *fn = id_to_path(fid);

                if (fn == NULL)
                    message(MSG_LEVEL_ERROR, "can't get filename, id = %d\n", fid);
                else
                    printf("%d\t%s\t%s\n", pos, name.c_str(), fn);
            }
            else
                message(MSG_LEVEL_ERROR, "can't parse def content: %s\n",
                        (const char *)val.data);
        }
        else
            message(MSG_LEVEL_ERROR, "can't get def, id = %d\n", id);
    }

    void print_ref_by_name(const char *name, int name_len)
    {
        // find ids from def_name table
        DBT key, val;

        memset(&key, 0, sizeof(DBT));
        memset(&val, 0, sizeof(DBT));

        key.data = (void *)name;
        key.size = name_len + 1;

        if (ref_name_cursor->get(ref_name_cursor, &key, &val, DB_SET) == 0) {
            do {
                int id = *(int *)val.data;

                print_ref_by_id(id);
            }
            while (ref_name_cursor->get(ref_name_cursor, &key, &val, DB_NEXT_DUP) == 0);
        }
    }

    void del_path_by_id(int id) {
        DBT key, val;

        memset(&key, 0, sizeof(DBT));
        memset(&val, 0, sizeof(DBT));

        key.data = (void *)&id;
        key.size = sizeof(int);

        if (path_cursor->get(path_cursor, &key, &val, DB_SET) == 0) {
            path_cursor->del(path_cursor, 0);
        }
    }

    int del_def_by_id(int          id,
                      int&         fid,
                      int&         pid,
                      int&         pos,
                      std::string& name,
                      std::string& type)
    {
        DBT key, val;

        memset(&key, 0, sizeof(DBT));
        memset(&val, 0, sizeof(DBT));

        key.data = (void *)&id;
        key.size = sizeof(int);

        int r = 0;

        if (def_cursor->get(def_cursor, &key, &val, DB_SET) == 0) {
            if (parse_def_content((const char *)val.data,
                                  fid, pid, pos, name, type) < 0) {
                fprintf(stderr, "parse def content failed: `%s'\n",
                        (const char *)val.data);
                r = -1;
            }

            def_cursor->del(def_cursor, 0);
        }
        else
            r = -1;

        return r;
    }

    int del_ref_by_id(int          id,
                      int&         fid,
                      int&         pos,
                      std::string& name)
    {
        DBT key, val;

        memset(&key, 0, sizeof(DBT));
        memset(&val, 0, sizeof(DBT));

        key.data = (void *)&id;
        key.size = sizeof(int);

        int r = 0;

        if (ref_cursor->get(ref_cursor, &key, &val, DB_SET) == 0) {
            if (parse_ref_content((const char *)val.data,
                                  fid, pos, name) < 0) {
                fprintf(stderr, "parse ref content failed: `%s'\n",
                        (const char *)val.data);
                r = -1;
            }

            ref_cursor->del(ref_cursor, 0);
        }
        else
            r = -1;

        return r;
    }

    void del_file(const char *fn, int fn_len)
    {
        // get the file id from table path_fn
        // name => id

        int fid = get_file_id_and_del(fn, fn_len);

        if (fid < 0) {
            return;
        }

        del_path_by_id(fid);

        // del def tag that fid it's
        // fid => id
        do {
            DBT key, val;

            memset(&key, 0, sizeof(DBT));
            memset(&val, 0, sizeof(DBT));

            key.data = (void *)&fid;
            key.size = sizeof(int);

            if (def_fid_cursor->get(def_fid_cursor, &key, &val, DB_SET) != 0)
                break;

            do {
                int id = *(int *)val.data;

                // get the def content from def table by id
                // and parse fields
                int fid, pid, pos;
                std::string name, type;

                if (del_def_by_id(id, fid, pid, pos, name, type) == 0) {
                    del_def_pid(id, pid);
                    del_def_name(id, name.c_str(), name.size());
                }

                // del the fid map table
                def_fid_cursor->del(def_fid_cursor, 0);
            }
            while (def_fid_cursor->get(def_fid_cursor, &key, &val, DB_NEXT_DUP) == 0);
        } while (0);

        // del ref tags...
        do {
            DBT key, val;

            memset(&key, 0, sizeof(DBT));
            memset(&val, 0, sizeof(DBT));

            key.data = (void *)&fid;
            key.size = sizeof(int);

            if (ref_fid_cursor->get(ref_fid_cursor, &key, &val, DB_SET) != 0)
                break;

            do {
                int id = *(int *)val.data;

                // get the def content from def table by id
                // and parse fields
                int         fid, pos;
                std::string name;

                if (del_ref_by_id(id, fid, pos, name) == 0) {
                    del_ref_name(id, name.c_str(), name.size());
                }

                // del the fid map table
                ref_fid_cursor->del(ref_fid_cursor, 0);
            }
            while (ref_fid_cursor->get(ref_fid_cursor, &key, &val, DB_NEXT_DUP) == 0);
        } while (0);
    }
};

struct TreeNode {
    int                         lineno;

    std::string                 name;

    std::string                 type;

    TreeNode                   *parent;

    std::vector<TreeNode *>     children;

    TreeNode() : parent(NULL)
    {
    }

    TreeNode(TreeNode *p) : parent(p)
    {
    }

    TreeNode(int l, const char *n, const char *t, TreeNode *p)
        : lineno(l), name(n), type(t), parent(p)
    {
    }

    ~TreeNode() {
        if (children.size() > 0) {
            int i;
            int nchildren = children.size();
            for (i = 0; i < nchildren; i++) {
                delete children[i];
            }
        }
    }

    TreeNode *create_child(int l, const char *n, const char *t) {
        TreeNode *tn = new TreeNode(l, n, t, this);

        children.push_back(tn);

        return tn;
    }

    static TreeNode *create_tree() {
        return new TreeNode();
    }

    static void destroy_free(TreeNode *tn) {
        delete tn;
    }
};

static void
_dump_tree(TreeNode *tn, int depth = 0)
{
    int i;
    for (i = 0; i < depth; i++) {
        printf("  ");
    }
    printf("%d\t%s\t%s\n", tn->lineno, tn->name.c_str(), tn->type.c_str());

    for (i = 0; i < (int) tn->children.size(); i++) {
        _dump_tree(tn->children[i], depth + 1);
    }
}

struct ProcessUserData {
    int                         last_size;

    std::vector<char *>         buffers;

    ProcessUserData() : last_size(0)
    {
    }

    ~ProcessUserData() {
        int i;
        for (i = 0; i < (int)buffers.size(); i++) {
            free(buffers[i]);
        }
    }

    void reset() {
        int i;
        for (i = 0; i < (int)buffers.size(); i++) {
            free(buffers[i]);
        }
        buffers.clear();
        last_size = 0;
    }

    int                         _p;

    int                         _i;

    void reset_get_char() {
        _p = _i = 0;
    }

    int get_char() {
        int page_max = ((_p + 1 < (int)buffers.size())
                        ? PAGE_SIZE : last_size);
        int r = -1;
        if (_i < page_max) {
            r = (unsigned char) buffers[_p][_i];
            ++_i;
            if (_i >= page_max) {
                if (_p + 1 < (int)buffers.size()) {
                    ++_p;
                    _i = 0;
                }
            }
        }
        return r;
    }

    void unget_char(int c) {
        if (_i > 0) {
            --_i;
            buffers[_p][_i] = c;
        }
        else if (_p > 0) {
            --_p;
            _i = PAGE_SIZE - 1;
            buffers[_p][_i] = c;
        }
    }
};

// return 0 parse nothing
// return 1 parsed a file tag tree
// return -1 invalid format
static int
_parse_tag_tree(ProcessUserData  *ud,
                std::string&      filename,
                TreeNode        **tree)
{
    int r;

    // get the file name
    while ((r = ud->get_char()) > 0 && r != '(')
        ;

    if (r <= 0)
        return 0;

    std::string instr;
    while ((r = ud->get_char()) > 0) {
        if (r == ' ')
            break;
        else if (r == '(' || r == ')') {
            message(MSG_LEVEL_ERROR, "invalid char, sl %d\n", __LINE__);
            return -1;
        }
        else
            instr.push_back((char)r);
    }

    if (instr != "change-file") {
        message(MSG_LEVEL_ERROR, "need change-file instr, but `%s'\n",
                instr.c_str());
        return -1;
    }

    filename.clear();
    while ((r = ud->get_char()) > 0) {
        if (r == ')')
            break;
        else if (r == '\\') {
            r = ud->get_char();
            if (r > 0)
                filename.push_back((char)r);
            else
                break;
        }
        else
            filename.push_back((char)r);
    }

    if (r != ')') {
        message(MSG_LEVEL_ERROR, "need `)' to term filename\n");
        return -1;
    }

    // goto file tag tree postion
    while ((r = ud->get_char()) > 0 && r != '(')
        ;

    // the tag tree is empty
    if (r != '(')
        return 0;

    TreeNode *root     = TreeNode::create_tree();
    TreeNode *cur_node = root;

    while ((r = ud->get_char()) > 0) {
        // tag.
        if (r >= '0' && r <= '9') {
            int lineno = r - '0';

            while ((r = ud->get_char()) > 0) {
                if (r >= '0' && r <= '9') {
                    lineno *= 10;
                    lineno += r - '0';
                }
                else if (r != ' ') {
                    TreeNode::destroy_free(root);
                    message(MSG_LEVEL_ERROR, "invalid char %c, sl %d\n", r, __LINE__);
                    return -1;
                }
                else
                    break;
            }

            if (r <= 0) {
                TreeNode::destroy_free(root);
                message(MSG_LEVEL_ERROR, "invalid r %d, sl %d\n", r, __LINE__);
                return -1;
            }

            std::string name;
            while ((r = ud->get_char()) > 0) {
                if (r == ' ')
                    break;
                else if (r == '\\') {
                    r = ud->get_char();
                    if (r > 0)
                        name.push_back((char)r);
                    else
                        break;
                }
                else
                    name.push_back((char)r);
            }

            if (r <= 0) {
                TreeNode::destroy_free(root);
                message(MSG_LEVEL_ERROR, "invalid r %d, sl %d\n", r, __LINE__);
                return -1;
            }

            std::string type;
            bool        follow = false;
            while ((r = ud->get_char()) > 0) {
                // end the tag
                if (r == ')')
                    break;
                else if (r == '(') {
                    follow = true;
                    ud->unget_char(r);
                    break;
                }
                else if (r == '\\') {
                    r = ud->get_char();
                    if (r > 0)
                        type.push_back((char)r);
                    else
                        break;
                }
                else
                    type.push_back((char)r);
            }

            if (r <= 0) {
                TreeNode::destroy_free(root);
                message(MSG_LEVEL_ERROR, "invalid r %d, sl %d\n", r, __LINE__);
                return -1;
            }

            TreeNode *tag = cur_node->create_child(lineno,
                                                   name.c_str(),
                                                   type.c_str());

            if (follow)
                cur_node = tag;
        }
        else if (r == ')') {
            // up
            if (cur_node->parent)
                cur_node = cur_node->parent;
        }
    }

    *tree = root;

    return 1;
}

static int g_path_id_cnt = 0;

static int g_def_id_cnt  = 0;

static int g_ref_id_cnt  = 0;

static Database *g_db;

static DB *
bdb_new(const std::string& fn, int cache_size = 0)
{
    DB  *db;
    int  ret;

    if (db_create(&db, NULL, 0))
        return NULL;

    if (cache_size > 0)
        db->set_cachesize(db, 0, cache_size, 0);

    ret = db->set_flags(db, DB_DUPSORT);

    if (ret != 0) {
        db->close(db, 0);
        return NULL;
    }

    if (db->open(db, NULL, fn.c_str(), NULL, DB_BTREE,
                 DB_CREATE
                 | DB_TRUNCATE
                 , 0)) {
        db->close(db, 0);
        return NULL;
    }

    return db;
}

static DB *
bdb_reopen(const std::string& fn)
{
    DB  *db;
    int  ret;

    if (db_create(&db, NULL, 0))
        return NULL;

    ret = db->set_flags(db, DB_DUPSORT);

    if (ret != 0) {
        db->close(db, 0);
        return NULL;
    }

    if (db->open(db, NULL, fn.c_str(), NULL, DB_BTREE, DB_CREATE, 0)) {
        db->close(db, 0);
        return NULL;
    }

    return db;
}

static void
bdb_close(DB *db)
{
    if (db) {
        db->close(db, 0);
    }
}

static int
bdb_put(DB *db,
        void *key, int key_len,
        void *val, int val_len)
{
    DBT k, v;

    memset(&k, 0, sizeof(DBT));
    memset(&v, 0, sizeof(DBT));

    k.data = key;
    k.size = key_len;

    v.data = val;
    v.size = val_len;

    return db->put(db, NULL, &k, &v, 0);
}

static int
_put_tag_tree_node(TreeNode *tn, int pid = -1)
{
    if (tn->name.size() > 0) {
        int          lineno = tn->lineno;
        std::string& name   = tn->name;
        std::string& type   = tn->type;

        char buf[1024];

        if (type == "reference") {
            // fid|pos|name

            ++g_ref_id_cnt;

            int len = snprintf(buf, 1024, "%d|%d|%s",
                               g_path_id_cnt,
                               lineno, name.c_str());
            if (bdb_put(g_db->ref,
                        (void *)&g_ref_id_cnt, sizeof(int),
                        (void *)buf, len + 1)) {
                fprintf(stderr, "db put ref failed\n");
                return -1;
            }

            if (bdb_put(g_db->ref_fid,
                        (void *)&g_path_id_cnt, sizeof(int),
                        (void *)&g_ref_id_cnt, sizeof(int))) {
                fprintf(stderr, "db put ref_fid failed\n");
                return -1;
            }

            if (bdb_put(g_db->ref_name,
                        (void *)name.c_str(), name.size() + 1,
                        (void *)&g_ref_id_cnt, sizeof(int))) {
                fprintf(stderr, "db put ref_name failed\n");
                return -1;
            }
        }
        else {
            // fid|pid|pos|name|type

            ++g_def_id_cnt;

            int len = snprintf(buf, 1024, "%d|%d|%d|%s|%s",
                               g_path_id_cnt,
                               pid, lineno,
                               name.c_str(), type.c_str());

            if (bdb_put(g_db->def,
                        (void *)&g_def_id_cnt, sizeof (int),
                        (void *)buf, len + 1)) {
                fprintf(stderr, "db put def failed\n");
                return -1;
            }

            if (bdb_put(g_db->def_fid,
                        (void *)&g_path_id_cnt, sizeof(int),
                        (void *)&g_def_id_cnt, sizeof(int))) {
                fprintf(stderr, "db put def_fid failed\n");
                return -1;
            }

            if (pid >= 0 &&
                bdb_put(g_db->def_pid,
                        (void *)&pid, sizeof(int),
                        (void *)&g_def_id_cnt, sizeof(int))) {
                fprintf(stderr, "db put def_pid failed\n");
                return -1;
            }

            if (bdb_put(g_db->def_name,
                        (void *)name.c_str(), name.size() + 1,
                        (void *)&g_def_id_cnt, sizeof(int))) {
                fprintf(stderr, "db put def_name failed\n");
                return -1;
            }
        }
    }

    if (tn->children.size() > 0) {
        int i;
        int ncs = tn->children.size();
        int this_pid = tn->name.empty() ? -1 : g_def_id_cnt;
        for (i = 0; i < ncs; i++) {
            if (_put_tag_tree_node(tn->children[i], this_pid) < 0)
                return -1;
        }
    }

    return 0;
}

static void parse_tag(ProcessUserData *ud)
{
    TreeNode    *root = NULL;
    std::string  fn;

    ud->reset_get_char();

    while (_parse_tag_tree(ud, fn, &root) == 1) {

        ++g_path_id_cnt;

        // delete the old tag
        if (g_mode == UPDATE_MODE) {
            g_db->del_file(fn.c_str(), fn.size());
        }

        // save the id <=> fn
        if (bdb_put(g_db->path,
                    (void *)&g_path_id_cnt, sizeof(int),
                    (void *)fn.c_str(), fn.size() + 1)) {
            fprintf(stderr, "db put path failed\n");
            break;
        }

        if (bdb_put(g_db->path_fn,
                    (void *)fn.c_str(), fn.size() + 1,
                    (void *)&g_path_id_cnt, sizeof(int))) {
            fprintf(stderr, "db put path_fn failed\n");
            break;
        }

        if (_put_tag_tree_node(root) < 0)
            break;

        TreeNode::destroy_free(root);
        root = NULL;
    }

    if (root)
        TreeNode::destroy_free(root);
}

#define _1M (1024 * 1024)
#define _1K (1024)

static Database *
create_db()
{
    Database *db = new Database();

    db->path     = bdb_new(g_db_root + DBFN_PATH,          _1M);
    db->path_fn  = bdb_new(g_db_root + DBFN_PATH_FN,       _1M);
    db->def      = bdb_new(g_db_root + DBFN_DEF,      50 * _1M);
    db->def_fid  = bdb_new(g_db_root + DBFN_DEF_FID,  20 * _1M);
    db->def_pid  = bdb_new(g_db_root + DBFN_DEF_PID,  20 * _1M);
    db->def_name = bdb_new(g_db_root + DBFN_DEF_NAME, 25 * _1M);
    db->ref      = bdb_new(g_db_root + DBFN_REF,      50 * _1M);
    db->ref_fid  = bdb_new(g_db_root + DBFN_REF_FID,  25 * _1M);
    db->ref_name = bdb_new(g_db_root + DBFN_REF_NAME, 25 * _1M);

    return db;
}

static Database *
reopen_db()
{
    Database *db = new Database();

    db->path     = bdb_reopen(g_db_root + DBFN_PATH);
    db->path_fn  = bdb_reopen(g_db_root + DBFN_PATH_FN);
    db->def      = bdb_reopen(g_db_root + DBFN_DEF);
    db->def_fid  = bdb_reopen(g_db_root + DBFN_DEF_FID);
    db->def_pid  = bdb_reopen(g_db_root + DBFN_DEF_PID);
    db->def_name = bdb_reopen(g_db_root + DBFN_DEF_NAME);
    db->ref      = bdb_reopen(g_db_root + DBFN_REF);
    db->ref_fid  = bdb_reopen(g_db_root + DBFN_REF_FID);
    db->ref_name = bdb_reopen(g_db_root + DBFN_REF_NAME);

    return db;
}

static void
close_db(Database *db)
{
    bdb_close(db->path);
    bdb_close(db->path_fn);
    bdb_close(db->def);
    bdb_close(db->def_fid);
    bdb_close(db->def_pid);
    bdb_close(db->def_name);
    bdb_close(db->ref);
    bdb_close(db->ref_fid);
    bdb_close(db->ref_name);

    delete db;
}

static int wait_procs()
{
    int exited;
    int exit_type;
    int exit_value;
    int cnt;
    int i;

    cnt = 0;
    for (i = 0; i < g_nprocesses; i++) {
        Process *proc = &processes[i];
        if (proc->running()) {
            if (proc->wait_proc(PROC_NO_WAIT, &exited, &exit_type, &exit_value) == false)
                continue;

            if (exited) {
                ++cnt;
                if (exit_type == PROC_EXIT_NORMAL) {
                    if (exit_value != 0)
                        message(MSG_LEVEL_MESSAGE, "process exit %d\n", exit_value);
                }
                else if (exit_type == PROC_EXIT_SIGNAL) {
                    message(MSG_LEVEL_ERROR, "process exit core dump\n");
                }
                else {
                    message(MSG_LEVEL_ERROR, "process got signal %d\n", exit_value);
                }
            }

            char *ptr;

            ProcessUserData *ud = (ProcessUserData *) proc->_user_data;

            if (ud) {
                for (;;) {
                    if (ud->buffers.empty()) {
                        ptr = (char *) malloc (PAGE_SIZE);
                        ud->buffers.push_back(ptr);
                        ud->last_size = 0;
                    }
                    else if (ud->last_size >= PAGE_SIZE) {
                        ptr = (char *) malloc (PAGE_SIZE);
                        ud->buffers.push_back(ptr);
                        ud->last_size = 0;
                    }
                    else
                        ptr = ud->buffers.back();

                    int bytes_to_read = PAGE_SIZE - ud->last_size;

                    int bytes_read = proc->read_stdout(ptr + ud->last_size,
                                                       bytes_to_read);

                    if (bytes_read > 0) {
                        ud->last_size += bytes_read;
                    }
                    else
                        break;
                }
            }
            else
                abort();

            for (;;) {
                // error message...
                char buf[4096];
                int n = proc->read_stderr(buf, 4096);
                if (n > 0) {
                    fwrite(buf, 1, n, stderr);
                }
                else
                    break;
            }

            if (exited) {
                parse_tag(ud);
            }
        }
    }
    return cnt;
}

static bool all_done()
{
    int i;
    for (i = 0; i < g_nprocesses; i++) {
        if (processes[i].running())
            return false;
    }
    return true;
}

bool lookup_cb(const char *filename)
{
    // the file must existed
    if (!test(filename, 'f')) {
        message(MSG_LEVEL_MESSAGE, "ignore `%s': not regular file\n", filename);
        return false;
    }

    pred_args[0]._str = filename;

    if (g_type_predicate_script.run_script_function("main",
                                                    pred_args,
                                                    pred_rets)) {
        if (pred_rets[0]._str.size() > 0) {
            std::string app = g_root;
            app.push_back(FS_SEP);
            app += "bin";
            app.push_back(FS_SEP);
            app += pred_rets[0]._str;

            // waiting for the free slot
            for (;;) {
                wait_procs();

                int i;
                for (i = 0; i < g_nprocesses; i++) {
                    if (processes[i].running() == false)
                        break;
                }

                if (i == g_nprocesses) {
                    sleep_ms(1);
                    continue;
                }

                StringVector args;
                args.push_back(filename);

                // attach the user data to the process
                if (processes[i]._user_data) {
                    ProcessUserData *ud = (ProcessUserData *) processes[i]._user_data;
                    ud->reset();
                }
                else
                    processes[i]._user_data = new ProcessUserData();

                processes[i].run(app.c_str(), args,
                                 PROC_BIND_STDOUT | PROC_BIND_STDERR);

                message(MSG_LEVEL_MESSAGE, "fetching `%s'\n", filename);

                break;
            }
        }
        else {
            //message(MSG_LEVEL_MESSAGE, "ignore `%s': not handle\n", filename);
        }
    }

    return true;
}

typedef void (*bdb_dump_func_t) (DBT *key, DBT *val);

void bdb_dump_str_str(DBT *key, DBT *val)
{
    printf("`%s' `%s'\n",
           (const char *)key->data,
           (const char *)val->data);
}

void bdb_dump_int_str(DBT *key, DBT *val)
{
    printf("%d `%s'\n",
           *(int *)key->data,
           (const char *)val->data);
}

void bdb_dump_str_int(DBT *key, DBT *val)
{
    printf("`%s' %d\n",
           (const char *)key->data,
           *(int *)val->data);
}

void bdb_dump_int_int(DBT *key, DBT *val)
{
    printf("%d %d\n",
           *(int *)key->data,
           *(int *)val->data);
}

static void
bdb_dump(const char *fn, bdb_dump_func_t fun)
{
    DB  *db;
    int  ret;

    if (db_create(&db, NULL, 0))
        return;

    ret = db->set_flags(db, DB_DUPSORT);

    if (ret != 0) {
        db->close(db, 0);
        return;
    }

    if (db->open(db, NULL, fn, NULL, DB_BTREE, DB_RDONLY, 0)) {
        db->close(db, 0);
        return;
    }

    DBC *cursor;

    db->cursor(db, NULL, &cursor, 0);

    DBT key, val;

    memset(&key, 0, sizeof(DBT));
    memset(&val, 0, sizeof(DBT));

    while ((ret = cursor->get(cursor, &key, &val, DB_NEXT)) == 0) {
        fun(&key, &val);
    }
}

static void
usage(const char *app) {
    (void) app;

    fprintf(stderr,
            "ytags --create|-c dir      fetch tags in dir recursive\n"
            "      --update|-u files    update single file\n"
            "      --query  -f filename show tags in file\n"
            "               -d token    show define tags\n"
            "               -r token    show reference tags\n"
            "      --find-db-dir        show the guess db path\n"
            "\n"
            "alter opts\n"
            "      --db-dir dir         store the tags in dir\n"
            "      --show-inherit       show the tags with inherit\n"
            "      --job|-j njobs       fork njobs to parse tags\n"
            "      --ignore-dir dir(s)  ignore directory(s)\n"
            "\n"
            "      --help|-h            show this help\n");
    exit(1);
}

static FILE *g_seq_fp = NULL;

static int seq_init() {
    if (g_mode == UPDATE_MODE)
        g_seq_fp = fopen((g_db_root + FN_SEQ).c_str(), "r+");
    else
        g_seq_fp = fopen((g_db_root + FN_SEQ).c_str(), "w+");

    if (g_seq_fp == NULL)
        fprintf(stderr, "open `%s' failed: %s\n", FN_SEQ, strerror(errno));

    return g_seq_fp ? 0 : -1;
}

static int seq_restore() {
    fseek(g_seq_fp, 0, SEEK_END);

    if (ftell(g_seq_fp) < (int) (sizeof(int) * 3))
        return -1;

    fseek(g_seq_fp, 0, SEEK_SET);

    fread(&g_path_id_cnt, 1, sizeof(int), g_seq_fp);
    fread(&g_def_id_cnt,  1, sizeof(int), g_seq_fp);
    fread(&g_ref_id_cnt,  1, sizeof(int), g_seq_fp);

    if (g_path_id_cnt < 0
        || g_def_id_cnt < 0
        || g_ref_id_cnt < 0)
        return -1;

    return 0;
}

static int seq_store() {
    fseek(g_seq_fp, 0, SEEK_SET);

    fwrite(&g_path_id_cnt, 1, sizeof(int), g_seq_fp);
    fwrite(&g_def_id_cnt, 1, sizeof(int), g_seq_fp);
    fwrite(&g_ref_id_cnt, 1, sizeof(int), g_seq_fp);

    return 0;
}

static void seq_done() {
    fclose(g_seq_fp);
}

static int
do_query(int opt_end, int argc, char *argv[])
{
    // -d def
    // -r ref
    // -f filename

    if (opt_end + 1 >= argc) {
        fprintf(stderr, "invalid usage\n");
        return 1;
    }

    const char *type = argv[opt_end];

    if (strcmp(type, "--def") == 0 || strcmp(type, "-d") == 0) {
        const char *name = argv[opt_end + 1];

        g_db->print_def_by_name(name, strlen(name));
    }
    else if (strcmp(type, "--ref") == 0 || strcmp(type, "-r") == 0) {
        const char *name = argv[opt_end + 1];

        g_db->print_ref_by_name(name, strlen(name));
    }
    else if (strcmp(type, "--file") == 0 || strcmp(type, "-f") == 0) {
        const char *name = argv[opt_end + 1];

        std::string abs_path;

        get_abs_path(name, abs_path);

        adjust_path(abs_path.c_str(), abs_path);

        g_db->print_def_by_filename(abs_path.c_str(),
                                    abs_path.size());
    }

    return 0;
}

int
str_split_by_chars(const char                *src,
                   const char                *seps,
                   std::vector<std::string>&  res)
{
    char        c;
    std::string str;

    for (;;) {
        c = *src++;

        if (c == 0 || strchr((char *) seps, c)) {
            if (str.size() > 0) {
                res.push_back(str);
                str.clear();
            }

            if (c) {
                while ((c = *src++) != 0) {
                    if (!strchr((char *) seps, c))
                        break;
                }

                if (c)
                    str.push_back(c);
            }

            if (!c)
                break;
        }
        else
            str.push_back(c);
    }

    return (int) res.size();
}

#define SKIP_HEADING_SPACE(ptr)                 \
    do {                                        \
        while (*ptr) {                          \
            if (CHAR_IS_WS(*ptr))               \
                ++ptr;                          \
            else                                \
                break;                          \
        }                                       \
    } while (0)

static int
handle_opt_arguments(int argc, char *argv[])
{
    // appname mode [opt arguments] [others]
    // start from 2
    int i;
    for (i = 2; i < argc; i++) {
        std::string p = argv[i];

        if (p == "--db-dir") {
            g_db_root = argv[++i];
        }
        else if (p == "--job" || p == "-j")
            g_nprocesses = atoi(argv[++i]);
        else if (p == "--show-inherit")
            g_show_inherit = 1;
        else if (p == "--ignore-dir") {
            if (i + 1 < argc) {
                const char *dirs = argv[++i];

                // split by `,'
                std::vector<std::string> res;
                str_split_by_chars(dirs, ",", res);

                int j;
                for (j = 0; j < (int) res.size(); j++) {
                    g_ignored_dir.push_back(res[j]);
                }
            }
        }
        else
            break;
    }

    return i;
}

// find the db dir from path
// ex: now ur cwd is /path/to/source/file
// and ur ytags.db in /path
// the function return 0 and set result to /path/ytags.db
// else return -1
static int
find_db_dir(const char *path, std::string& result)
{
    std::string t(path);

    for (;;) {
        if (t.empty())
            break;

        result = t;
        result.push_back(FS_SEP);
        result += "ytags.db";

        if (test(result.c_str(), 'd'))
            return 0;

        int i = t.size() - 1;

        while (i >= 0 && t[i] != FS_SEP)
            --i;

        if (i < 0)
            break;

        t.resize(i);
    }

    return -1;
}

int main(int argc, char *argv[])
{
    const char *app = argv[0];

    if (argc < 2)
        usage(app);

    // update mode or crate mode
    const char *mode = argv[1];

    if (strcmp(mode, "--create") == 0 || strcmp(mode, "-c") == 0)
        g_mode = CREATE_MODE;
    else if (strcmp(mode, "--update") == 0 || strcmp(mode, "-u") == 0)
        g_mode = UPDATE_MODE;
    else if (strcmp(mode, "--query") == 0 || strcmp(mode, "-q") == 0)
        g_mode = QUERY_MODE;
    else if (strcmp(mode, "--find-db-dir") == 0) {
        std::string path;
        if (argc == 3) {
            get_abs_path(argv[2], path);

            if (adjust_path(path.c_str(), path) < 0) {
                fprintf(stderr, "adjust path failed\n");
                return 1;
            }
        }
        else {
            if (get_cwd(path) < 0) {
                fprintf(stderr, "getcwd failed\n");
                return 1;
            }
        }

        std::string res;

        if (find_db_dir(path.c_str(), res) == 0) {
            printf("%s\n", res.c_str());
            return 0;
        }

        return 1;
    }
    else
        usage(app);

    if (setup_root() < 0)
        return 1;

    if (setup_type_predicate_script() < 0)
        return 1;

    int opt_arg_end = handle_opt_arguments(argc, argv);

    if (g_db_root.empty()) {
        if (g_mode == CREATE_MODE)
            g_db_root = "ytags.db";
        else {
            std::string pwd;

            get_cwd(pwd);

            if (find_db_dir(pwd.c_str(), g_db_root) < 0)
                g_db_root = "ytags.db";
        }
    }

    {
        std::string abs_path;

        get_abs_path(g_db_root.c_str(), abs_path);

        if (adjust_path(abs_path.c_str(), g_db_root) < 0) {
            fprintf(stderr, "adjust db-dir failed: dir = `%s'\n", abs_path.c_str());
            return 1;
        }
    }

    if (g_mode == CREATE_MODE) {
        // create the data dir
        if (create_dir(g_db_root.c_str()) < 0) {
            fprintf(stderr, "create dir ytags.db failed: %s\n", strerror(errno));
            return 1;
        }
    }

    g_db_root.push_back(FS_SEP);

    // open the db
    if (g_mode == CREATE_MODE) {
        g_db = create_db();
    }
    else {
        g_db = reopen_db();
    }

    if (g_db->all_opened_p() == false) {
        fprintf(stderr, "open database failed\n");
        close_db(g_db);
        return 1;
    }

    g_db->create_cursors();

    if (g_mode == QUERY_MODE) {
        return do_query(opt_arg_end, argc, argv);
    }

    LuaArg s;
    s._type = LUA_TYPE_STR;
    pred_args.push_back(s);
    pred_rets.push_back(s);

    // create the processes space
    if (g_nprocesses <= 0)
        g_nprocesses = 2;

    processes = new Process[g_nprocesses];

    // init seq
    if (seq_init() < 0) {
        fprintf(stderr, "init seq error\n");
        return 1;
    }

    if (g_mode == CREATE_MODE) {
        if (opt_arg_end >= argc)
            usage(app);

        const char *path = argv[opt_arg_end];

        std::string abs_path;

        if (get_abs_path(path, abs_path) < 0)
            return 1;

        // adjust the path...
        if (adjust_path(abs_path.c_str(), abs_path) < 0) {
            fprintf(stderr, "adjust path `%s' failed\n", abs_path.c_str());
            return 1;
        }

        dir_lookup(abs_path.c_str(), lookup_cb);
    }
    else {
        int i;

        if (seq_restore() < 0) {
            fprintf(stderr, "restore seq error\n");
            return 1;
        }

        for (i = opt_arg_end; i < argc; i++) {
            const char *path = argv[i];

            std::string abs_path;

            if (get_abs_path(path, abs_path) < 0)
                return 1;

            // adjust the path...
            if (adjust_path(abs_path.c_str(), abs_path) < 0) {
                fprintf(stderr, "adjust path `%s' failed\n", abs_path.c_str());
                continue;
            }

            lookup_cb(abs_path.c_str());
        }
    }

    while (!all_done()) {
        sleep_ms(1);
        wait_procs();
    }

    g_db->close_cursors();
    close_db(g_db);

    if (seq_store() < 0) {
        fprintf(stderr, "store seq error\n");
        return 1;
    }

    seq_done();

    // dump db

    // path
//     printf(">>> dumping %s\n", DBFN_PATH);
//     bdb_dump(DBFN_PATH, bdb_dump_int_str);
//     printf(">>> dumping %s\n", DBFN_PATH_FN);
//     bdb_dump(DBFN_PATH_FN, bdb_dump_str_int);

    // def
//     printf(">>> dumping %s\n", DBFN_DEF);
//     bdb_dump(DBFN_DEF, bdb_dump_int_str);
//     printf(">>> dumping %s\n", DBFN_DEF_FID);
//     bdb_dump(DBFN_DEF_FID, bdb_dump_int_int);
//     printf(">>> dumping %s\n", DBFN_DEF_PID);
//     bdb_dump(DBFN_DEF_PID, bdb_dump_int_int);
//     printf(">>> dumping %s\n", DBFN_DEF_NAME);
//     bdb_dump(DBFN_DEF_NAME, bdb_dump_str_int);

    // ref
//     printf(">>> dumping %s\n", DBFN_REF);
//     bdb_dump(DBFN_REF, bdb_dump_int_str);
//     printf(">>> dumping %s\n", DBFN_REF_FID);
//     bdb_dump(DBFN_REF_FID, bdb_dump_int_int);
//     printf(">>> dumping %s\n", DBFN_REF_NAME);
//     bdb_dump(DBFN_REF_NAME, bdb_dump_str_int);

    return 0;
}
